In this notebook we test our Khovanov homology obstruction for positivity on the low-crossing knots (of crossing number at most 12) and on the census knots (for which we know the Khovanov homology).
We start with the low crossing knots. For that we load the KnotInfo data.
import snappy
import csv
knot_info=[]
with open('knotinfoKH.csv', 'r') as file:
reader = csv.reader(file)
for row in reader:
knot_info.append(row)
print(knot_info[0])
KH.<q,t,T> = LaurentPolynomialRing(ZZ,3)
KNOTINFO=[]
for x in knot_info[1:]:
data=[x[0],x[1],x[2],x[3]]
for i in [4,5,6,7,8,9,10]:
string=x[i]
s=string.replace('^','**')
s=s.replace(' ','')
s=s.replace(')t',')*t')
s=s.replace(')q',')*q')
s=s.replace(')T',')*T')
s=s.replace('qt','q*t')
s=s.replace('0t','0*t')
s=s.replace('1t','1*t')
s=s.replace('2t','2*t')
s=s.replace('3t','3*t')
s=s.replace('4t','4*t')
s=s.replace('5t','5*t')
s=s.replace('6t','6*t')
s=s.replace('7t','7*t')
s=s.replace('8t','8*t')
s=s.replace('9t','9*t')
s=s.replace('0q','0*q')
s=s.replace('1q','1*q')
s=s.replace('2q','2*q')
s=s.replace('3q','3*q')
s=s.replace('4q','4*q')
s=s.replace('5q','5*q')
s=s.replace('6q','6*q')
s=s.replace('7q','7*q')
s=s.replace('8q','8*q')
s=s.replace('9q','9*q')
s=s.replace('qt','q*t')
s=s.replace('tq','t*q')
s=s.replace('t(','t*(')
s=s.replace(')t',')*T')
s=s.replace('Qt','Q*T')
s=s.replace('0t','0*T')
s=s.replace('1t','1*T')
s=s.replace('2t','2*T')
s=s.replace('3t','3*T')
s=s.replace('4t','4*T')
s=s.replace('5t','5*T')
s=s.replace('6t','6*T')
s=s.replace('7t','7*T')
s=s.replace('8t','8*T')
s=s.replace('9t','9*T')
s=s.replace('0Q','0*Q')
s=s.replace('1Q','1*Q')
s=s.replace('2Q','2*Q')
s=s.replace('3Q','3*Q')
s=s.replace('4Q','4*Q')
s=s.replace('5Q','5*Q')
s=s.replace('6Q','6*Q')
s=s.replace('7Q','7*Q')
s=s.replace('8Q','8*Q')
s=s.replace('9Q','9*Q')
s=s.replace('Qt','Q*T')
s=s.replace('tQ','T*Q')
s=s.replace('QT','Q*T')
s=s.replace('TQ','T*Q')
s=s.replace('t(','T*(')
kh=KH(eval(s))
data.append(kh)
KNOTINFO.append(data)
def KH_positivity_obstruction(KH_poly):
'''If K is a positive knot the following properties are known about its Khovanov homologies KH^(i,j):
(1) KH^(i,j)=0 if i<0
(2) KH^(0,2g-1)=F where g is the genus of K (for a link -Eulercharacteristic)
(3) KH^(0,2g+1)=F
(4) KH^(1,2g+1)=F^p1 where p1 is the chromatic number of the reduced Seifert graph of a positive diagram
(5) KH^(1,2g+1)=0 if and only if K is fibered
(6) KH^(0,j)=0 for all other j
(7) KH^(1,j)=0 for all other j
(8) KH^(i,2g-1)=0 for all other i
(9) KH^(0,2g+1)=0 for all other i
This function takes as input a Khovanov polynomial of a knot. And outputs False if any of these conditins
is not fulfilled. (Except for checking that the genus is correct and the fiberedness condition.)
Otherwise it will print the above non-vanishing terms, together with its possible fiberedness status, and genus.
By computing the fiberedness status and the genus
(for example by computing the knot Floer homology) the above obstructions can sometimes be improved.
'''
fiberedness=None
try:
kh=t*KH_poly.derivative(t)(q,0,0)+KH_poly(q,0,0)
except ZeroDivisionError:
return False
exp=kh.exponents()
if len(exp)>3:
return False
if len(exp)<2:
return False
if len(exp)==3:
fiberedness=False
g=int((exp[2][0]+1)/2)
if exp[2][1]!=0:
return False
if exp[1][1]!=0:
return False
if exp[1][0]!=2*g+1:
return False
if exp[0][0]!=2*g+1:
return False
if exp[0][1]!=1:
return False
if len(exp)==2:
fiberedness=True
g=int((exp[1][0]+1)/2)
if exp[1][1]!=0:
return False
if exp[0][1]!=0:
return False
if exp[0][0]!=2*g+1:
return False
pol=KH_poly
for count in range(0,2*g-1):
pol=pol.derivative(q)
try:
qmin=q**(2*g-1)*pol(0,t,0)
except ZeroDivisionError:
return False
if len(qmin.exponents())!=1:
return False
pol=pol.derivative(q)
pol=pol.derivative(q)
try:
qalmmin=q**(2*g+1)*pol(0,t,0)
except ZeroDivisionError:
return False
if fiberedness:
if len(qalmmin.exponents())!=1:
return False
if fiberedness==False:
if len(qalmmin.exponents())!=2:
return False
return [kh,fiberedness,g]
def KH1(KH_poly):
'''Returns KH in homological grading 1 (if KH is non-negative).'''
try:
return KH_poly.derivative(t)(q,0,0)
except ZeroDivisionError:
return False
First we verify that our obstructions are fullfilled for positive knots:
for x in KNOTINFO:
if x[3]=='Y':
if KH_positivity_obstruction(x[4])==False:
print('Obstruction is NOT working for:',x[1])
else:
[kh,fib,genus]=KH_positivity_obstruction(x[4])
knot_floer=snappy.Manifold('K'+x[1].replace('_','')).link().knot_floer_homology()
if fib!=knot_floer.get('fibered'):
print('Obstruction is NOT working for:',x[1])
if genus!=knot_floer.get('seifert_genus'):
print('Obstruction is NOT working for:',x[1])
print(x[1],kh)
Next, we check how strong our obstructions are. For that we look at the non-positive knots and print their Khovanov homology if it looks like the Khovanov homology of a positive knot.
for x in KNOTINFO:
if x[3]!='Y':
if KH_positivity_obstruction(x[4])!=False:
[kh,fib,genus]=KH_positivity_obstruction(x[4])
knot_floer=snappy.Manifold('K'+x[1].replace('_','')).link().knot_floer_homology()
if fib==knot_floer.get('fibered'):
if genus==knot_floer.get('seifert_genus'):
print(x[1],kh)
if KH_positivity_obstruction(x[4](q^(-1),t^(-1),T))!=False:
[kh,fib,genus]=KH_positivity_obstruction(x[4](q^(-1),t^(-1),T))
knot_floer=snappy.Manifold('K'+x[1].replace('_','')).link().knot_floer_homology()
if fib==knot_floer.get('fibered'):
if genus==knot_floer.get('seifert_genus'):
print(x[1],kh)
Thus for the low-crossing knots (i.e. with crossing number at most 12) Khovanov homology detects positive knots.
We also observe from the KnotInfo data that such an obstruction might hold in reduced Khovanov homology and odd Khovanov homology.
We continue checking this for the census knots. We first prepare the data.
KHs=[]
with open('khovanov_most_recent.csv', 'r') as file:
reader = csv.reader(file)
for row in reader:
KHs.append([row[1],row[2]])
KHs=KHs[1:]
len(KHs)
S.<q,t> = LaurentPolynomialRing(ZZ,2); # We set up the polynomial ring:
KH=[]
for [name,s] in KHs:
KH.append([name,S(eval(s.replace('^','**')))]) #different sign convention
len(KH)
fibered=[]
with open('fibered.csv', 'r') as file:
reader = csv.reader(file)
for row in reader:
fibered.append([row[0],row[1]])
fibered=fibered[1:]
len(fibered)
positive=[]
with open('positivity.csv', 'r') as file:
reader = csv.reader(file)
for row in reader:
positive.append([row[0],row[1]])
positive=positive[1:]
len(positive)
Seifert_genus=[]
with open('3genus.csv', 'r') as file:
reader = csv.reader(file)
for row in reader:
Seifert_genus.append([row[0],row[1]])
Seifert_genus=Seifert_genus[1:]
len(Seifert_genus)
CENSUS_KNOTS=[]
for x in KH:
for y in fibered:
if x[0]==y[0]:
break
for z in positive:
if x[0]==z[0]:
break
for g in Seifert_genus:
if g[0]==x[0]:
break
CENSUS_KNOTS.append([x[0],z[1],y[1],g[1],x[1]]) #name,pos,fib,genus,KH
len(CENSUS_KNOTS)
def KH_positivity_obstruction(KH_poly):
'''If K is a positive knot the following properties are known about its Khovanov homologies KH^(i,j):
(1) KH^(i,j)=0 if i<0
(2) KH^(0,2g-1)=F where g is the genus of K (for a link -Eulercharacteristic)
(3) KH^(0,2g+1)=F
(4) KH^(1,2g+1)=F^p1 where p1 is the chromatic number of the reduced Seifert graph of a positive diagram
(5) KH^(1,2g+1)=0 if and only if K is fibered
(6) KH^(0,j)=0 for all other j
(7) KH^(1,j)=0 for all other j
(8) KH^(i,2g-1)=0 for all other i
(9) KH^(0,2g+1)=0 for all other i
This function takes as input a Khovanov polynomial of a knot. And outputs False if any of these conditins
is not fulfilled. (Except for checking that the genus is correct and the fiberedness condition.)
Otherwise it will print the above non-vanishing terms, together with its possible fiberedness status, and genus.
By computing the fiberedness status and the genus
(for example by computing the knot Floer homology) the above obstructions can sometimes be improved.
'''
fiberedness=None
poly=S(KH_poly.dict())
try:
kh=t*poly.derivative(t)(q,0)+poly(q,0)
except ZeroDivisionError:
return False
exp=kh.exponents()
if len(exp)>3:
return False
if len(exp)<2:
return False
if len(exp)==3:
fiberedness=False
g=int((exp[2][0]+1)/2)
if exp[2][1]!=0:
return False
if exp[1][1]!=0:
return False
if exp[1][0]!=2*g+1:
return False
if exp[0][0]!=2*g+1:
return False
if exp[0][1]!=1:
return False
if len(exp)==2:
fiberedness=True
g=int((exp[1][0]+1)/2)
if exp[1][1]!=0:
return False
if exp[0][1]!=0:
return False
if exp[0][0]!=2*g+1:
return False
pol=poly
for count in range(0,2*g-1):
pol=pol.derivative(q)
try:
qmin=q**(2*g-1)*pol(0,t)
except ZeroDivisionError:
return False
if len(qmin.exponents())!=1:
return False
pol=pol.derivative(q)
pol=pol.derivative(q)
try:
qalmmin=q**(2*g+1)*pol(0,t)
except ZeroDivisionError:
return False
if fiberedness:
if len(qalmmin.exponents())!=1:
return False
if fiberedness==False:
if len(qalmmin.exponents())!=2:
return False
return [kh,fiberedness,g]
def KH1(KH_poly):
'''Returns KH in homological grading 1 (if KH is non-negative).'''
try:
return KH_poly.derivative(t)(q,0)
except ZeroDivisionError:
return False
We start by verifying the obstruction for the positive census knots:
for x in CENSUS_KNOTS:
if x[1]=='True':
if KH_positivity_obstruction(x[4])==False:
if KH_positivity_obstruction(x[4](q^(-1),t^(-1)))==False:
print('Obstruction is NOT working for:',x[0])
else:
[kh,fib,genus]=KH_positivity_obstruction(x[4](q^(-1),t^(-1)))
else:
[kh,fib,genus]=KH_positivity_obstruction(x[4])
if x[2]!=str(fib):
print('Obstruction is NOT working for:',x[0])
if x[3]!=str(genus):
print('Obstruction is NOT working for:',x[0])
print(x[0],kh)
Next, we check our obstructions on the knots with previously unknown positivity status:
for x in CENSUS_KNOTS:
if x[1]=='':
print(x[0])
obs=False
if KH_positivity_obstruction(x[4])==False:
if KH_positivity_obstruction(x[4](q^(-1),t^(-1)))==False:
print('is NOT positive, since Khovanov homology has the wrong form. It is:')
print(x[4])
obs=True
else:
[kh,fib,genus]=KH_positivity_obstruction(x[4](q^(-1),t^(-1)))
else:
[kh,fib,genus]=KH_positivity_obstruction(x[4])
if obs==False:
print('has KH in degree 0 and 1:',kh)
print('Seifert genus:',x[3])
print('Fiberedness status:',x[2])
if x[3]!=str(genus):
print('is NOT positive since its Seifert genus is:',x[3])
if x[2]!=str(fib):
print('is NOT positive since its fiberedness status is:',x[2])
print('------------------')
So this obstructs 3 knots from being positive for which all previously known obstructions where not working:
v1964 is not positive since it is not fibered but its first Khovanov homology is 0.
v2743 is not positive since its Khovanov homology has the wrong form.
o9_34097 is not positive since it is not fibered but its first Khovanov homology is 0.
We check also how strong the obstruction is on the other census knots that are known to be non-positive.
for x in CENSUS_KNOTS:
if x[1]=='False':
if KH_positivity_obstruction(x[4])!=False:
[kh,fib,genus]=KH_positivity_obstruction(x[4])
if str(fib)==x[2]:
if str(genus)==x[3]:
print(x[0])
print('has KH in degree 0 and 1:',kh)
print('Seifert genus:',x[3])
print('and fiberedness status:',x[2])
print('------------------------')
if KH_positivity_obstruction(x[4](q^(-1),t^(-1)))!=False:
[kh,fib,genus]=KH_positivity_obstruction(x[4](q^(-1),t^(-1)))
if str(fib)==x[2]:
if str(genus)==x[3]:
print(x[0])
print('has KH in degree 0 and 1:',kh)
print('Seifert genus:',x[3])
print('and fiberedness status:',x[2])
print('------------------------')
I.e. Khovanov homology obstructs among the positive census knots with known Khovanov homology all but 9 knots from being positive.